import java.io.IOException;
import java.util.*;
import java.io.*;
/**insert into Ck
 * Select from p.item1, pitem2, ....p.itemk-1, q.itemk-1
 * from Lk-1 p, Lk-1 q
 * where p.item1..... p.itemk-1<q.itemk-1
 * 
 * forall itemsets c// ck do
 * for all k-1 subsets s of c do 
 * if s not belong to lk-1 then
 * delete c from ck.
 * 
 * 
 * Since we can add only one item in each iteration, maybe we can optimize 
 * the join procedure.
 * */

public class Apriori {

	private Map<Integer, List<Itemset>> pathMap;
	
	private List<Transaction> transaction;
	private int trSize;
	
	public Apriori(String file){
		
		pathMap=new HashMap<Integer,List<Itemset>>();
		transaction=new ArrayList<Transaction>();
		try {
			DataLoader.load(file, transaction, pathMap);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		trSize=transaction.size();
	}
	/**This would implement the SECTION 2.2.1 algorithm in the reference paper.
	 * @param sup the min support of the assocation rule.
	 * */
	public void aprioriRun(double sup) throws IOException{
		
		int k=1;
		
		// remove k=1
		double minsup=((double)transaction.size())*sup;
		
		Iterator<Itemset> itt=pathMap.get(1).iterator();
		
		while(itt.hasNext()){
		    Itemset tmp=itt.next();
		    if(tmp.getCount()<minsup){
		    	itt.remove();
		    }else{
		    	double sp=(double) tmp.getCount()/(double)trSize;
		    	tmp.setSup(sp);
		    }
		}
		
		Collections.sort(pathMap.get(1));
		
		System.out.println(pathMap.get(1).size());
		try {
			Thread.sleep(1000);
			System.out.println("Continute");
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		while(aprioriGen(pathMap.get(k),k)){
			
			
			System.out.println("inside continute");
			
			System.out.println(k+1+"\t"+pathMap.get(k+1).size());
			
			for(Transaction ts:transaction){
				
				Itemset iSet=ts.getItemSet();
				
				Iterator<Itemset> it=pathMap.get(k+1).iterator();
				
				while(it.hasNext()){
					Itemset iTSet=it.next();
					//reference?
					if(iSet.getItems().containsAll(iTSet.getItems())){
						iTSet.addCount();
					}
				}
				
			}
			
			Iterator<Itemset> it=pathMap.get(k+1).iterator();	
			
			while(it.hasNext()){
				Itemset iSet=it.next();
				
				if(iSet.getCount()<minsup){
					it.remove();
				}else{
					iSet.setSup((double)iSet.getCount()/(double)trSize);
				}
			}
			
			System.out.println("after remove:\t"+pathMap.get(k+1).size());
			
			k++;
		}
		
		//output.txt
		File output=new File("output.txt");
		FileWriter out=new FileWriter(output, false);
		
		List<Itemset> largeItemSet=new ArrayList<Itemset>();
			
		for(int i=1;i<=pathMap.keySet().size();i++){
				
				largeItemSet.addAll(pathMap.get(i));	
			//	pathMap.remove(i);
		}
		
		Collections.sort(largeItemSet, new counComparator());
		
		Iterator<Itemset> it=largeItemSet.iterator();
		out.write("==============Large Itemsets(min_sup="+sup*100+"%)\n\n");
		while(it.hasNext()){
			Itemset iSet=it.next();
			out.write(iSet.toString());
		}
		out.close();
		largeItemSet.clear();
		
	}	
	/**This method would caculate the min confience of each potential rule, and 
	 * add the satisfied rule to the out put file. It would call the static method 
	 * Confidence.calConf. Note that large itemset with only one item won't be add to
	 * any rule.
	 * @param minConf the required min confidence.
	 * */
	public void aprioiConf(double minConf) throws IOException{
		
	    List<Itemset> largeItemSet=new ArrayList<Itemset>();
	    
	    for(Integer i:pathMap.keySet()){
	    	int j=i;
	    	if(j==1){
	    		continue;
	    	}else{
	    		largeItemSet.addAll(pathMap.get(i));
	    	}
	    }	
		
		Confidence.calConf(largeItemSet, transaction, minConf);
		
	}
	/**The submethod in Apriori Algorithm, which run the item extention step.
	 * It would join the itemset generated by last step.
	 * @param iList, the itemset generated by last step.
	 * @param k, the number of itemset of iList.
	 * */
	@SuppressWarnings("unchecked")
	public boolean aprioriGen(List<Itemset> iList, int k){
		
		List<Itemset> candidate=new ArrayList<Itemset> ();
		
		int size=iList.size();
		
		System.out.println("in Gen:\t"+k+"\t"+size);
		for(int i=0;i<size;){
			
			int count=groupCount(iList,i);
			
			//System.out.println("group count"+count);
			if(count==0){
				i++;
			}else{
				
				for(int t=i;t<i+count;t++){
					
					for(int j=t+1;j<=i+count;j++){
						
						Itemset iSet=new Itemset();	
						iSet.addItems(iList.get(t).getItems(), iList.get(j).getItems());
						//System.out.println("item size:\t"+iSet.size());
						candidate.add(iSet);
					}
				}							
			}
			
			i=i+count+1;		
		}
		
		//prune the itemSet;
		List<Itemset> LkE=new ArrayList<Itemset>();
		
		for(Itemset iSet:candidate){
			
			boolean flag=true;	
			
			for(String ss:iSet.getItems()){
				
				Itemset subSet=new Itemset();
				Set<String> subStringSet=new TreeSet<String>();
				subStringSet.addAll(iSet.getItems());
				subStringSet.remove(ss);
				
				subSet.addItems(subStringSet);
				
				if(!iList.contains(subSet)){
					
					flag=false;
					break;
				}
			}
			
			if(flag){
				LkE.add(iSet);
			}
				
		}
		
		if(LkE.isEmpty()){
			
			return false;
		}
		else{
			
		   Collections.sort(LkE);
		   pathMap.put(k+1, LkE);
		   return true;
		}
	}
	
	/**To make our algorithm we would order the itemsets, and count how many sequential
	 * itemsets have the same first k-1 item.
	 * @param iList, the list of itemset contain k items.
	 * @param i the current position.
	 * */
	public int groupCount(List<Itemset> iList, int i){
		
		int count=0;
		int size=iList.size();
		
		for(int j=i+1;j<size;j++){
			
			if(iList.get(i).prefixEquals(iList.get(j))){
			   count++;
			}
			else{
				
				return count;
			}
		}
	//	System.out.println(count);
		return count;
		
	}
	
	public String toString(){
		
		StringBuilder sb=new StringBuilder();
		
		for(Integer it:pathMap.keySet()){
			
			sb.append(pathMap.get(it));	
			sb.append("\n");
		}	
		return sb.toString();	
	}
	
	public static void main(String args[]) throws InterruptedException, IOException{
	   
		if(args.length!=3){
	    	System.out.println("invalid input, please check your input!");
	    	System.out.println("./run.sh datafile sup conf.");
	    	System.exit(0);
	    }
		String file=args[0];
		Apriori ar=new Apriori(file);
		double sup=Double.parseDouble(args[1]);
		ar.aprioriRun(sup);	
		System.out.println(ar);
		double conf=Double.parseDouble(args[2]);
		ar.aprioiConf(conf);
	}
}
